package com.lincoln.framework.utils.qrcode;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Hashtable;
import java.util.Iterator;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import javax.servlet.http.HttpServletResponse;

import jp.sourceforge.qrcode.QRCodeDecoder;
import jp.sourceforge.qrcode.exception.DecodingFailedException;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;
import com.swetake.util.Qrcode;

/**
 * 二维码 工具
 * 
 * @author Rubekid
 * 
 */
public class QRcodeUtils {

	/**
	 * 默认version
	 */
	public static final int DEFAULT_VERSION = 4;

	/**
	 * 图片类型 png (默认) 比较小
	 */
	public static final String IMAGE_TYPE_PNG = "png";

	/**
	 * 图片类型 jpg
	 */
	public static final String IMAGE_TYPE_JPEG = "jpg";

	/**
	 * 容错率等级 L
	 */
	public static final char CORRECT_L = 'L';

	/**
	 * 容错率等级 M
	 */
	public static final char CORRECT_M = 'M';

	/**
	 * 容错率等级 Q
	 */
	public static final char CORRECT_Q = 'Q';

	/**
	 * 容错率等级 H
	 */
	public static final char CORRECT_H = 'H';

	/**
	 * 默认容错率
	 */
	public static final char DEFAULT_CORRECT = CORRECT_M;

	/**
	 * 旁白占用比率
	 */
	public static final double PADDING_RATIO = 0.02;

	/**
	 * 生成二维码
	 * 
	 * @param content
	 *            要生成的内容
	 * @param size
	 *            要生成的尺寸
	 * @param version
	 *            二维码尺寸，取值范围1-40，值越大尺寸越大，可存储的信息越大
	 * @param ecc
	 *            二维码排错率，可选L(7%)、M(15%)、Q(25%)、H(30%)
	 * @return
	 * @throws Exception
	 */
	public static BufferedImage encode(String content, int size, int version, char ecc) throws Exception {
		BufferedImage image = null;
		Qrcode qrcode = new Qrcode();
		if (!isValidEcc(ecc)) {
			ecc = DEFAULT_CORRECT;
		}
		if (version < 1 || version > 40) {
			version = DEFAULT_VERSION;
		}

		// 设置二维码排错率，可选L(7%)、M(15%)、Q(25%)、H(30%)，排错率越高可存储的信息越少，但对二维码清晰度的要求越小
		qrcode.setQrcodeErrorCorrect(ecc);
		qrcode.setQrcodeEncodeMode('B');
		// 设置设置二维码尺寸，取值范围1-40，值越大尺寸越大，可存储的信息越大
		qrcode.setQrcodeVersion(version);
		// 获得内容的字节数组，设置编码格式
		byte[] contentBytes = content.getBytes("utf-8");

		// 输出内容 二维码
		boolean[][] codeOut = null;

		if (contentBytes.length > 0) {
			while (codeOut == null && version <= 40) {
				try {
					codeOut = qrcode.calQrcode(contentBytes);
				} catch (Exception exception) {
					qrcode.setQrcodeVersion(++version);
				}
			}
		}
		if (codeOut != null) {
			double ratio = 3;// 默认放大三倍
			int qrcodeSize = 21 + 4 * (version - 1);
			if (size > 0) {
				ratio = (double) size / qrcodeSize;
			}
			int intRatio = (int) Math.ceil(ratio);

			// 旁白
			int padding = (int) Math.ceil(qrcodeSize * intRatio * PADDING_RATIO);
			if (padding < 5) {
				padding = 5;
			}
			// 图片尺寸
			int imageSize = qrcodeSize * intRatio + padding * 2;

			// 图片边长 调整（10的倍数）
			if (intRatio > ratio) {
				int newSize = (int) Math.ceil(ratio * imageSize / intRatio);
				int r = newSize % 10;
				newSize += 10 - r;
				int newImageSize = (int) Math.floor(intRatio * newSize / ratio);
				padding += (newImageSize - imageSize) / 2;
				imageSize = newImageSize;
			} else {
				int r = imageSize % 10;
				int d = 10 - r;
				padding += d / 2;
				imageSize += d;
			}

			image = new BufferedImage(imageSize, imageSize, BufferedImage.TYPE_INT_RGB);
			Graphics2D gs = image.createGraphics();
			// 设置背景颜色
			gs.setBackground(Color.WHITE);
			gs.clearRect(0, 0, imageSize, imageSize);

			// 设定图像颜色 BLACK
			gs.setColor(Color.BLACK);

			for (int i = 0; i < codeOut.length; i++) {
				for (int j = 0; j < codeOut.length; j++) {
					if (codeOut[j][i]) {
						gs.fillRect(j * intRatio + padding, i * intRatio + padding, intRatio, intRatio);
					}
				}
			}

			gs.dispose();
			image.flush();

			if (intRatio > ratio) {
				image = resize(image, (int) Math.ceil(ratio * imageSize / intRatio));
			}
		}
		return image;
	}

	/**
	 * 生成二维码
	 * 
	 * @param content
	 * @param size
	 * @return
	 * @throws Exception
	 */
	public static BufferedImage encode(String content, int size) throws Exception {
		return encode(content, size, DEFAULT_VERSION, DEFAULT_CORRECT);
	}

	/**
	 * 生成二维码
	 * 
	 * @param content
	 * @param size
	 * @param version
	 * @return
	 */
	public static BufferedImage encode(String content, int size, int version) throws Exception {
		return encode(content, size, version, DEFAULT_CORRECT);
	}

	/**
	 * 生成二维码
	 * 
	 * @param content
	 * @param size
	 * @param ecc
	 * @return
	 */
	public static BufferedImage encode(String content, int size, char ecc) throws Exception {
		return encode(content, size, DEFAULT_VERSION, ecc);
	}

	/**
	 * 生成二维码
	 * 
	 * @param content
	 * @return
	 */
	public static BufferedImage encode(String content) throws Exception {
		return encode(content, 0, DEFAULT_VERSION, DEFAULT_CORRECT);
	}

	/**
	 * 解析二维码
	 * 
	 * @throws UnsupportedEncodingException
	 * @throws DecodingFailedException
	 */
	public static String decode(BufferedImage bufferedImage) throws DecodingFailedException,
			UnsupportedEncodingException {
		QRCodeDecoder decoder = new QRCodeDecoder();
		return new String(decoder.decode(new MyQRCodeImage(bufferedImage)), "utf-8");
	}

	/**
	 * 下载二维码
	 * 
	 * @param filename
	 * @param content
	 * @param size
	 * @param imageType
	 * @throws Exception
	 */
	public static void download(HttpServletResponse response, String filename, String content, int size,
			String imageType, String logoPath) throws Exception {

		if (size > 2000) {
			size = 2000;
		}
		BufferedImage image = QRcodeUtils.encode(content, size);
		if (logoPath != null) {
			markLogo(image, logoPath);
		}
		// 替换占位符
		filename = filename.replace("{size}", String.valueOf(image.getWidth()));
		InputStream inputStream = toInputStream(image, imageType);
		int length = inputStream.available();

		// 设置response
		response.setContentType("application/x-msdownload");
		response.setContentLength(length);
		response.setHeader("Content-Disposition", "attachment;filename="
				+ new String(filename.getBytes("gbk"), "iso-8859-1"));

		// 输出流
		byte[] bytes = new byte[1024];
		OutputStream outputStream = response.getOutputStream();
		while (inputStream.read(bytes) != -1) {
			outputStream.write(bytes);
		}
		outputStream.flush();
	}

	/**
	 * 下载二维码
	 * 
	 * @param filename
	 * @param content
	 * @param size
	 * @param imageType
	 * @throws Exception
	 */
	public static void download(HttpServletResponse response, String filename, String content, int size,
			String imageType) throws Exception {
		download(response, filename, content, size, imageType, null);
	}

	/**
	 * 下载二维码
	 * 
	 * @param filename
	 * @param content
	 * @param size
	 * @throws Exception
	 */
	public static void download(HttpServletResponse response, String filename, String content, int size)
			throws Exception {
		download(response, filename, content, size, IMAGE_TYPE_PNG);
	}

	/**
	 * 生成二维码文件
	 * 
	 * @param destFile
	 * @param content
	 * @param size
	 * @param version
	 * @param ecc
	 * @param logoPath
	 * @throws Exception
	 */
	public static void createQrcodeFile(File destFile, String content, int size, int version, char ecc, String logoPath)
			throws Exception {
		if (!destFile.exists() || !destFile.isFile()) {
			destFile.createNewFile();
		}
		OutputStream outputStream = new FileOutputStream(destFile);
		BufferedImage image = QRcodeUtils.encode(content, size, version, ecc);
		if (logoPath != null) {
			markLogo(image, logoPath);
		}
		InputStream inputStream = toInputStream(image, QRcodeUtils.IMAGE_TYPE_PNG);
		// 输出流
		byte[] bytes = new byte[1024];
		while (inputStream.read(bytes) != -1) {
			outputStream.write(bytes);
		}
		inputStream.close();
		outputStream.close();
	}

	/**
	 * 生成二维码文件
	 * 
	 * @param destFile
	 * @param content
	 * @param size
	 * @param logoPath
	 * @throws Exception
	 */
	public static void createQrcodeFile(File destFile, String content, int size, String logoPath) throws Exception {
		createQrcodeFile(destFile, content, size, DEFAULT_VERSION, DEFAULT_CORRECT, logoPath);
	}

	/**
	 * 生成二维码文件
	 * 
	 * @param destFile
	 * @param content
	 * @param size
	 * @throws Exception
	 */
	public static void createQrcodeFile(File destFile, String content, int size) throws Exception {
		createQrcodeFile(destFile, content, size, null);
	}

	/**
	 * 显示二维码图片
	 * 
	 * @param content
	 * @param size
	 * @param logoPath
	 * @throws Exception
	 */
	public static void image(HttpServletResponse response, String content, int size, String logoPath) throws Exception {

		if (size > 2000) {
			size = 2000;
		}
		BufferedImage image = QRcodeUtils.encode(content, size);
		if (logoPath != null) {
			markLogo(image, logoPath);
		}
		// 替换占位符

		InputStream inputStream = toInputStream(image, "png");
		int length = inputStream.available();

		// 设置response
		response.setContentType("image/png;charset=urf-8");
		response.setContentLength(length);

		// 输出流
		byte[] bytes = new byte[1024];
		OutputStream outputStream = response.getOutputStream();
		while (inputStream.read(bytes) != -1) {
			outputStream.write(bytes);
		}
		outputStream.flush();
	}

	/**
	 * 显示二维码图片
	 *
	 * @param response
	 * @param content
	 * @param size
	 * @throws Exception
	 */
	public static void image(HttpServletResponse response, String content, int size) throws Exception {
		image(response, content, size, null);
	}

	/**
	 * logo水印
	 * 
	 * @param qrcode
	 * @param logoPath
	 * @throws IOException
	 */
	public static void markLogo(BufferedImage qrcode, String logoPath) throws IOException {
		File logoFile = new File(logoPath);
		if (logoFile.exists()) {
			Graphics2D gs = qrcode.createGraphics();
			int width = qrcode.getWidth();
			int height = qrcode.getHeight();
			Image logo = ImageIO.read(logoFile);

			int scale = 6; // 显示为图片的 1/n;

			// logo显示大小
			int lw = width / scale;
			int lh = height / scale;
			int wx = (width - lw) / 2;
			int wy = (height - lh) / 2;
			gs.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1));
			gs.drawImage(logo, wx, wy, lw, lh, null); // 图片水印文件结束
			gs.dispose();
			qrcode.flush();
		}

	}

	/**
	 * 获取内容二维码图片出流
	 * 
	 * @param content
	 * @param size
	 * @param imageType
	 * @return
	 * @throws Exception
	 *             InputStream
	 */
	public static InputStream getQrcodeImageStream(String content, int size, String imageType, String logoPath)
			throws Exception {
		if (size > 2000) {
			size = 2000;
		}
		BufferedImage image = QRcodeUtils.encode(content, size);
		if (logoPath != null) {
			markLogo(image, logoPath);
		}
		return toInputStream(image, imageType);
	}

	/**
	 * 转成InputStream
	 * 
	 * @param image
	 * @param imageType
	 * @return
	 * @throws IOException
	 */
	private static InputStream toInputStream(BufferedImage image, String imageType) throws IOException {
		// BufferedImage 转 InputStream
		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
		ImageOutputStream imageOutput = ImageIO.createImageOutputStream(byteArrayOutputStream);

		// 生成jpg格式
		if (IMAGE_TYPE_JPEG.equals(imageType)) {
			Iterator<ImageWriter> iterator = ImageIO.getImageWritersByFormatName("jpeg");
			ImageWriter imageWriter = iterator.next();
			ImageWriteParam imageWriteParam = imageWriter.getDefaultWriteParam();
			imageWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
			imageWriteParam.setCompressionQuality(1); // 设置质量
			imageWriter.setOutput(imageOutput);
			IIOImage iio_image = new IIOImage(image, null, null);
			imageWriter.write(null, iio_image, imageWriteParam);
			imageWriter.dispose();
		} else {
			ImageIO.write(image, "png", imageOutput);
		}
		return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
	}

	/**
	 * 图片缩放
	 */
	private static BufferedImage resize(BufferedImage image, int size) {
		int imageSize = (size % 2) > 0 ? (size + 1) : size;

		BufferedImage bufferedImage = new BufferedImage(imageSize, imageSize, BufferedImage.TYPE_INT_RGB);
		Graphics2D gs = bufferedImage.createGraphics();
		gs.setBackground(Color.WHITE);
		gs.clearRect(0, 0, imageSize, imageSize);
		gs.setColor(Color.BLACK);
		gs.fillRect(0, 0, imageSize, imageSize);
		gs.drawImage(image, 0, 0, size, size, null);
		gs.dispose();
		image.flush();
		return bufferedImage;
	}

	/**
	 * 生成二维码
	 * 
	 * @param content
	 *            二维码内容
	 * @param width
	 *            二维码宽度
	 * @param height
	 *            二维码高度
	 * @param destFile
	 *            保存路径
	 * @throws Exception
	 *             void
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static void createQRcodeImage(String content, int width, int height, File destFile) throws Exception {
		Hashtable hints = new Hashtable();
		hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
		hints.put(EncodeHintType.MARGIN, 0);
		BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints);
		MatrixToImageWriter.writeToFile(bitMatrix, IMAGE_TYPE_PNG, destFile);
	}

	/**
	 * 校验ecc
	 */
	public static boolean isValidEcc(char ecc) {
		if (ecc == CORRECT_H || ecc == CORRECT_Q || ecc == CORRECT_M || ecc == CORRECT_L) {
			return true;
		}
		return false;
	}
}
